package com.example.utility.crypto.mac;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;
import java.util.Random;

/**
 * @author 季舟
 * @Date 2025-01-02 17:55
 */
public class BasedHmacAppKeySecretDemo {
    /**
     * 主函数执行流程：
     * 1. 初始化appKey和appSecret。
     * 2. 构建URL，包含数据、时间戳和随机数。
     * 3. 使用HMACSHA256算法生成签名，并将其添加到URL中。
     * 4. 输出完整的URL。
     * 5. 从URL中提取数据和签名，分别打印。
     * 6. 验证签名的有效性，并打印验证结果。
     * 7. 根据时间戳验证请求是否过期。
     * 8. 使用Redis等缓存服务存储随机数，用于防止重放攻击。
     */
    public static void main(String[] args) {
        // 初始化appKey和appSecret，这里使用示例值
        String appKey = "yourAppKey";
        String appSecret = "yourAppSecret";

        // 初始化URL并添加示例数据
        String url = "?data=310765287101876542";
        // 获取当前时间戳，用于生成唯一请求标识
        Date date = new Date();
        long timestampMillisDate = date.getTime();
        url += "&" + "timestamp=" + timestampMillisDate;
        // 生成随机数，增加请求的唯一性
        Random random = new Random();
        int randomIntRange = random.nextInt(10000);
        url += "&" + "nonce=" + randomIntRange;
        // 使用appSecret对URL进行HMACSHA256签名，确保请求的完整性
        String signature = generateHMACSHA256(url, appSecret);
        url += "&" + "signature=" + signature;
        // 打印最终构建的URL
        System.out.println("客户端发送的数据：" + url);

        // 从URL中提取数据部分
        String data = url.substring(0, url.indexOf("&signature"));
        // 从URL中提取签名部分
        String signature1 = url.substring(url.indexOf("&signature") + "&signature".length() + 1);
        // 分别打印提取出的数据和签名
        System.out.println("服务端截取数据部分：" + data);
        System.out.println("服务端截取签名部分：" + signature1);
        // 验证签名的有效性，并打印验证结果
        boolean verifySignature = verifySignature(data, appSecret, signature1);
        System.out.println("服务端验证签名是否成功：" + verifySignature);
        if (!verifySignature) {
            return;
        }

        String nonce = data.substring(data.indexOf("&nonce") + "&nonce".length() + 1);
        System.out.println("服务端截取随机数部分：" + nonce);
        String timestamp = data.substring(data.indexOf("&timestamp") + "&timestamp".length() + 1, data.indexOf("&nonce"));
        System.out.println("服务端截取时间戳部分：" + timestamp);
        long serverTimestampMillisDate = new Date().getTime();
        long timestampServerClient = serverTimestampMillisDate - Long.valueOf(timestamp);
        System.out.println("服务器时间：" + serverTimestampMillisDate + ",客户端时间：" + timestamp + ",时间差：" + timestampServerClient);
        if (serverTimestampMillisDate - Long.valueOf(timestamp) > 30000) {
            System.out.println("请求超时");
            return;
        }

        // 使用nonce作为key，从redis中查找是否存在
        // 若存在，则判断为重放攻击
        // 若不存在，则将nonce存入redis，key为nonce，value为timestamp，设置过期时间为30秒

    }

    /**
     * 使用HMAC-SHA256算法生成消息认证码
     *
     * @param data 要保护的数据
     * @param key  密钥，用于生成消息认证码
     * @return 以Base64编码的HMAC-SHA256消息认证码
     * @throws RuntimeException 如果生成过程中出现异常
     */
    public static String generateHMACSHA256(String data, String key) {
        try {
            // 创建一个SecretKeySpec对象，使用指定的密钥和算法
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            // 获取Mac实例，并初始化
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(secretKeySpec);
            // 计算哈希值
            byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            // 将哈希值转换为Base64编码的字符串
            return Base64.getEncoder().encodeToString(hmacBytes);
        } catch (Exception e) {
            throw new RuntimeException("生成HMAC-SHA256失败", e);
        }
    }

    /**
     * 验证签名的正确性
     *
     * @param data      要验证的数据字符串
     * @param appSecret 应用密钥，用于生成签名
     * @param signature 需要验证的签名字符串
     * @return 如果签名验证成功返回true，否则返回false
     */
    public static boolean verifySignature(String data, String appSecret, String signature) {
        // 生成基于给定数据和应用密钥的HMAC SHA256签名
        String signature1 = generateHMACSHA256(data, appSecret);
        // 输出生成的签名，用于调试或日志记录
        //System.out.println(signature1);
        // 比较生成的签名与提供的签名是否一致
        // 如果签名不一致，返回false表示验证失败
        return signature1.equals(signature);
        // 如果签名一致，返回true表示验证成功
    }
}
